package com.demo.security.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import com.demo.security.config.bean.RsaProperties;
import com.demo.security.custom.SysUserDetailsService;
import com.demo.security.custom.filter.CaptchaVerifyFilter;
import com.demo.security.custom.filter.JwtTokenVerifyFilter;
import com.demo.security.custom.handler.MyAccessDeniedHandler;
import com.demo.security.custom.handler.MyAuthenticationEntryPoint;
import com.demo.security.custom.handler.MyAuthenticationFailureHandler;
import com.demo.security.custom.handler.MyAuthenticationSuccessHandler;
import com.demo.security.custom.handler.MyLogoutSuccessHandler;
import com.demo.security.utils.SpringUtils;

@Configuration
@EnableWebSecurity(debug = false)
@PropertySource("classpath:security-config.properties")
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private RsaProperties rsaProperties;

    @Value("${security.permit.urls:}")
    private String[] permitURLs;
    @Value("${security.anonymous.urls:}")
    private String[] anonymousURLs;
    @Value("${security.ignore.urls:}")
    private String[] ignoreURLs;

    @Override
    protected void configure(HttpSecurity security) throws Exception {
        // 关闭csrf保护
        security.csrf().disable();
        // 关闭跨域保护
        security.cors().disable();

        security.authorizeRequests()
                // 允许任何访问的URL
                .antMatchers(permitURLs).permitAll()
                // 允许匿名访问的URL(已登录的用户不能访问!!!)
                .antMatchers(anonymousURLs).anonymous()
                // 需要特定"角色授权"才能访问的方法(在我们返回的UserDetails的Authority需要加ROLE_前缀，Controller上使用时不要加前缀)
                //.antMatchers("/addUser").hasAnyRole("admin", "user")
                // 需要特定"权限授权"才能访问的方法(用户自定义的权限，返回的UserDetails的Authority只要与这里匹配就可以)
                .antMatchers("/deleteUser").hasAnyAuthority("admin", "user")
                // 除以上定义外，其他需要鉴权
                .anyRequest().authenticated();

        // 前后端分离，禁用session，采用Token进行登录认证
        // always – 如果session不存在总是需要创建；
        // ifRequired – 仅当需要时，创建session(默认配置)
        // never – 框架从不创建session，但如果已经存在，会使用该session
        // stateless – Spring Security不会创建session，或使用session
        security.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

        // Web登录配置
        security.formLogin()
                // 自定义登录页面
                //.loginPage("/templates/login.html")
                // 登录成功跳转URL(如果前后端分离，不应配置该项)
                //.defaultSuccessUrl("/templates/home.html")
                // 登录失败URL(如果前后端分离，不应配置该项)
                // .failureUrl("/templates/error.html")
                // 处理登录的URL(默认POST /login)
                //.loginProcessingUrl("/login")
                // 设置登录登录的用户名和密码参数名
                //.usernameParameter("username").passwordParameter("password");
                // 登录成功的处理器(直接访问登录页面，登录成功后会调用该handler处理，如果是其它路径被拦截后跳转到登录页面，登录成功后会自动重定向回原路径)
                .successHandler(SpringUtils.getBean(MyAuthenticationSuccessHandler.class))
                // 登录失败的处理器
                .failureHandler(SpringUtils.getBean(MyAuthenticationFailureHandler.class));

        // 配置登出
        security.logout()
                // 处理注销的URL(默认/logout)
                //.logoutUrl("/logout")
                // 注销成功跳转的URL(如果前后端分离，不应配置该项)
                // .logoutSuccessUrl("/login")
                // 注销成功的处理器
                .logoutSuccessHandler(SpringUtils.getBean(MyLogoutSuccessHandler.class));

        // 添加自定义异常处理
        security.exceptionHandling().authenticationEntryPoint(new MyAuthenticationEntryPoint());
        security.exceptionHandling().accessDeniedHandler(new MyAccessDeniedHandler());

        // 添加账号密码登录过滤器(该过滤器默认只会处理POST /login请求)
        // http.addFilter(new MyUsernamePasswordAuthentication(super.authenticationManager()));
        // 添加Token验证过滤器
        security.addFilter(new JwtTokenVerifyFilter(super.authenticationManager(), rsaProperties));
        security.addFilterBefore(SpringUtils.getBean(CaptchaVerifyFilter.class), UsernamePasswordAuthenticationFilter.class);
    }

    //配置用户
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        SysUserDetailsService userDetailsService = SpringUtils.getBean(SysUserDetailsService.class);
        auth.userDetailsService(userDetailsService).passwordEncoder(this.passwordEncoder());
    }

    // 忽略静态资源，不会参与认证
    @Override
    public void configure(WebSecurity security) throws Exception {
        // super.configure(web);
        // 放行所有OPTIONS请求。
        security.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");

        // WebSecurity主要是配置跟web资源相关的，比如css、js、images等
        // ingore是完全绕过了spring security的所有filter，相当于不走spring security。就是把一些静态资源开放给前端
        security.ignoring().antMatchers(ignoreURLs);
    }

    // 密码编码器
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    public static void main(String[] args) {
        System.out.println(new BCryptPasswordEncoder().encode("123456"));
    }
}
